package orchestration.android;

import java.util.HashMap;

import orchestration.businessprocess.execution.BPExecution;
import orchestration.persistence.OrchestratorAuthority;
import orchestration.persistence.OrchestratorAuthority.AndroidProcess;
import orchestration.persistence.OrchestratorAuthority.AndroidProcessExecution;
import orchestration.persistence.OrchestratorAuthority.AndroidProcessExecutionProperty;
import orchestration.persistence.sqlite.OrchestratorDatabaseHelper;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;

/**
 * A content provider for Orchestrator main data.
 * It is possible to retrieve information about processes and executions.
 * @author  <a href="mailto:car.ferrarini@gmail.com">Carlo Ferrarini</a>
 */
public class ProcessProvider extends ContentProvider {
	
    private static final UriMatcher sUriMatcher;
    private static final int PROCESSES=1;
    private static final int PROCESS_ID=2;
    private static final int EXECUTIONS=3;
    private static final int EXECUTION_ID=4;
    
    private static HashMap<String, String> sProcessesProjectionMap;
    private static HashMap<String, String> sExecutionsProjectionMap;
    
    /**
	 * @uml.property  name="mOpenHelper"
	 * @uml.associationEnd  
	 */
    private OrchestratorDatabaseHelper mOpenHelper;
    
    @Override
    public boolean onCreate() {
        mOpenHelper = new OrchestratorDatabaseHelper(getContext());
        return true;
    }
    /**
     * SQLite DB doesn't support foreign key constraints, so we need to delete manually
     * - properties when we delete executions
     * - executions when we delete processes
     */
	@Override
	public int delete(Uri uri, String where, String[] whereArgs) {
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count;
        switch (sUriMatcher.match(uri)) {
        case PROCESSES:
        	//deleting executions
        	Cursor procCursor= db.query(OrchestratorDatabaseHelper.TABLE_PROCESS, new String []{AndroidProcess._ID}, where, whereArgs,null,null,null);        	
        	procCursor.moveToFirst();
        	while(!procCursor.isAfterLast()){
        		long procId=procCursor.getLong(procCursor.getColumnIndex(AndroidProcess._ID));
        		delete(AndroidProcessExecution.CONTENT_URI, AndroidProcessExecution.PROCESS_REF+"=?",new String[]{""+procId});
        		procCursor.moveToNext();
        	}
        	procCursor.close();
        	//delete processes
        	count = db.delete(OrchestratorDatabaseHelper.TABLE_PROCESS, where, whereArgs);
            break;
        	
        case PROCESS_ID:
        	String processId = uri.getPathSegments().get(1);
        	//delete executions
        	Cursor procIdCursor=db.query(OrchestratorDatabaseHelper.TABLE_PROCESS, new String []{AndroidProcess._ID},
        			AndroidProcess._ID + "=" + processId + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs, null, null, null);
        	procIdCursor.moveToFirst();
        	if(!procIdCursor.isAfterLast())
        		delete(AndroidProcessExecution.CONTENT_URI, AndroidProcessExecution.PROCESS_REF+"=?",new String[]{processId});
        	procIdCursor.close();
            //delete process
            count = db.delete(OrchestratorDatabaseHelper.TABLE_PROCESS, AndroidProcess._ID + "=" + processId
                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
            break;
        case EXECUTIONS:
        	//delete properties
        	Cursor execCursor= db.query(OrchestratorDatabaseHelper.TABLE_EXECUTION, new String[]{AndroidProcessExecution._ID}, where, whereArgs, null, null,	 null);
        	execCursor.moveToFirst();
        	while(!execCursor.isAfterLast()){
        		long execId=execCursor.getLong(execCursor.getColumnIndex(AndroidProcessExecution._ID));
        		db.delete(OrchestratorDatabaseHelper.TABLE_PROPERTIES, AndroidProcessExecutionProperty.EXECUTION_REF+"=?",new String[]{""+execId});
        		execCursor.moveToNext();
        	}
        	execCursor.close();
        	//delete executions
        	count = db.delete(OrchestratorDatabaseHelper.TABLE_EXECUTION, where, whereArgs);
            break;
        	
        case EXECUTION_ID:
        	String executionId = uri.getPathSegments().get(1);
        	//delete properties
        	Cursor execIdCursor=db.query(OrchestratorDatabaseHelper.TABLE_EXECUTION, new String []{AndroidProcessExecution._ID},
        			AndroidProcessExecution._ID + "=" + executionId + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs, null, null, null);
        	execIdCursor.moveToFirst();
        	if(!execIdCursor.isAfterLast())
        		db.delete(OrchestratorDatabaseHelper.TABLE_PROPERTIES, AndroidProcessExecutionProperty.EXECUTION_REF+"=?",new String[]{executionId});            
            execIdCursor.close();
        	//delete execution
        	count = db.delete(OrchestratorDatabaseHelper.TABLE_EXECUTION, AndroidProcessExecution._ID + "=" + executionId
                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }

        getContext().getContentResolver().notifyChange(uri, null);
        return count;
	}

	@Override
	public String getType(Uri uri) {
		switch (sUriMatcher.match(uri)) {
        case PROCESSES:
            return AndroidProcess.CONTENT_TYPE;

        case PROCESS_ID:
            return AndroidProcess.CONTENT_ITEM_TYPE;
        case EXECUTIONS:
            return AndroidProcessExecution.CONTENT_TYPE;

        case EXECUTION_ID:
            return AndroidProcessExecution.CONTENT_ITEM_TYPE;
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
	}
	
	@Override
	public Uri insert(Uri uri, ContentValues initialValues) {
		ContentValues values;
		long rowId=0;
		SQLiteDatabase db;
		Uri contentUri;
		if (initialValues != null) {
			values = new ContentValues(initialValues);
		} else {
			values = new ContentValues();
		}
		switch(sUriMatcher.match(uri)){
        case PROCESSES:
	        if (values.containsKey(AndroidProcess.NAME) == false) {
	            values.put(AndroidProcess.NAME, "");
	        }
	
	        if (values.containsKey(AndroidProcess.PATH) == false) {
	            values.put(AndroidProcess.PATH, "");
	        }
	        db = mOpenHelper.getWritableDatabase();
			rowId = db.insert(OrchestratorDatabaseHelper.TABLE_PROCESS, AndroidProcess.PATH, values);
			contentUri=AndroidProcess.CONTENT_URI;
	        break;
        case EXECUTIONS:
        	if (values.containsKey(AndroidProcessExecution.NAME) == false) {
	            Resources r = Resources.getSystem();
	            values.put(AndroidProcessExecution.NAME, r.getString(android.R.string.untitled));
	        }
        	if (values.containsKey(AndroidProcessExecution.EXECUTION_STATUS) == false) {
	            values.put(AndroidProcessExecution.EXECUTION_STATUS, BPExecution.STATUS_NEW);
	        }
        	if (values.containsKey(AndroidProcessExecution.TOVISIT) == false) {
	            values.put(AndroidProcessExecution.TOVISIT, "");
	        }
        	if (values.containsKey(AndroidProcessExecution.VISITED) == false) {
	            values.put(AndroidProcessExecution.VISITED, "");
	        }
        	if (values.containsKey(AndroidProcessExecution.PROCESS_REF) == false) {
	            throw new IllegalArgumentException("cannot save a process execution that doesn't refer to any process");
        	}
        	db = mOpenHelper.getWritableDatabase();
    		rowId = db.insert(OrchestratorDatabaseHelper.TABLE_EXECUTION, AndroidProcess.PATH, values);
    		contentUri=AndroidProcessExecution.CONTENT_URI;
    		break;
    	default:
    		throw new IllegalArgumentException("Unknown URI " + uri);		
        }
		if (rowId > 0) {
			Uri newUri = ContentUris.withAppendedId(contentUri, rowId);
			getContext().getContentResolver().notifyChange(newUri, null);
			return newUri;
		}			
        throw new SQLException("Failed to insert row into " + uri);	
	}

	 @Override
	    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
	            String sortOrder) {
	        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

	        switch (sUriMatcher.match(uri)) {
	        case PROCESSES:
	            qb.setTables(OrchestratorDatabaseHelper.TABLE_PROCESS);
	            qb.setProjectionMap(sProcessesProjectionMap);
	            break;

	        case PROCESS_ID:
	            qb.setTables(OrchestratorDatabaseHelper.TABLE_PROCESS);
	            qb.setProjectionMap(sProcessesProjectionMap);
	            qb.appendWhere(AndroidProcess._ID + "=" + uri.getPathSegments().get(1));
	            break;
	        case EXECUTIONS:
	        	qb.setTables(OrchestratorDatabaseHelper.TABLE_EXECUTION);
	            qb.setProjectionMap(sExecutionsProjectionMap);
	            break;
	        case EXECUTION_ID:
	            qb.setTables(OrchestratorDatabaseHelper.TABLE_EXECUTION);
	            qb.setProjectionMap(sExecutionsProjectionMap);
	            qb.appendWhere(AndroidProcessExecution._ID + "=" + uri.getPathSegments().get(1));
	            break;
	        default:
	            throw new IllegalArgumentException("Unknown URI " + uri);     
	        }
	        
	        // If no sort order is specified use the default
	        String orderBy;
	        if (TextUtils.isEmpty(sortOrder)) {
	            orderBy = AndroidProcess.DEFAULT_SORT_ORDER;
	        } else {
	            orderBy = sortOrder;
	        }

	        // Get the database and run the query
	        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
	        Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);

	        // Tell the cursor what uri to watch, so it knows when its source data changes
	        c.setNotificationUri(getContext().getContentResolver(), uri);
	        return c;
	        
	 }
	 
	@Override
	public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        int count;
        switch (sUriMatcher.match(uri)) {
        case PROCESSES:
            count = db.update(OrchestratorDatabaseHelper.TABLE_PROCESS, values, where, whereArgs);
            break;

        case PROCESS_ID:
            String processId = uri.getPathSegments().get(1);
            count = db.update(OrchestratorDatabaseHelper.TABLE_PROCESS, values, AndroidProcess._ID + "=" + processId
                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
            break;
        case EXECUTIONS:
            count = db.update(OrchestratorDatabaseHelper.TABLE_EXECUTION, values, where, whereArgs);
            break;

        case EXECUTION_ID:
            String executionId = uri.getPathSegments().get(1);
            count = db.update(OrchestratorDatabaseHelper.TABLE_EXECUTION, values, AndroidProcess._ID + "=" + executionId
                    + (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
            break;
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }

        getContext().getContentResolver().notifyChange(uri, null);
        return count;
	}

	static {
		//processes
		sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		sUriMatcher.addURI(OrchestratorAuthority.AUTHORITY, "processes", PROCESSES);
		sUriMatcher.addURI(OrchestratorAuthority.AUTHORITY, "processes/#", PROCESS_ID);
		sProcessesProjectionMap = new HashMap<String, String>();
		sProcessesProjectionMap.put(AndroidProcess._ID,AndroidProcess._ID);
		sProcessesProjectionMap.put(AndroidProcess.NAME,AndroidProcess.NAME);
		sProcessesProjectionMap.put(AndroidProcess.PATH,AndroidProcess.PATH);
		//executions
		sUriMatcher.addURI(OrchestratorAuthority.AUTHORITY, "process_executions", EXECUTIONS);
		sUriMatcher.addURI(OrchestratorAuthority.AUTHORITY, "process_executions/#", EXECUTION_ID);
		sExecutionsProjectionMap = new HashMap<String, String>();
		sExecutionsProjectionMap.put(AndroidProcessExecution._ID,AndroidProcessExecution._ID);
		sExecutionsProjectionMap.put(AndroidProcessExecution.NAME,AndroidProcessExecution.NAME);
		sExecutionsProjectionMap.put(AndroidProcessExecution.PROCESS_REF,AndroidProcessExecution.PROCESS_REF);
		sExecutionsProjectionMap.put(AndroidProcessExecution.TOVISIT,AndroidProcessExecution.TOVISIT);
		sExecutionsProjectionMap.put(AndroidProcessExecution.VISITED,AndroidProcessExecution.VISITED);
		sExecutionsProjectionMap.put(AndroidProcessExecution.EXECUTION_STATUS,AndroidProcessExecution.EXECUTION_STATUS);
		sExecutionsProjectionMap.put(AndroidProcessExecution.CURRENT,AndroidProcessExecution.CURRENT);
		sExecutionsProjectionMap.put(AndroidProcessExecution.KEY,AndroidProcessExecution.KEY);
		
	}
}